該系列是為了讓看過Vue官方文件或學過Vue但是卻不知道怎麼下手去重構現在有的網站而去規畫的系列文章,在這邊整理了許多我自己使用Vue重構很多網站的經驗分享給讀者們。
我們在開發平台的時候常常會有需要上傳商品圖片的需求,但是往往我們會需要確定我們上傳的圖片到底對不對,或是要做一些修改確認,這時候預覽的功能就很重要,在以前,前端的預覽功能會需要後端先把圖片存起來後,在前端在顯示出來,或是透過已經死去的 Flash 來達成預覽的功能,但是當html5的崛起,又出現了可以透過 canvas 的方式來做預覽的動作,但是相對來說 canvas 相對複雜一點,所以後來就出現了新的 API 可以幫我們完成前端預覽的功能。
URL.createObjectURL()
: 可以將我們 input 所選取的 FIle 物件轉換成 Blob 物件給瀏覽器讀取。MDN 文件:https://developer.mozilla.org/zh-TW/docs/Web/API/URL/createObjectURL
我們先來看一下我們要完成的樣子長什麼樣
當我今天 click 畫面上的 button 的時候,會選取我的圖片,然後確認會把這些圖片變成預覽圖放到畫面上。
首先我們來看一下我們要怎麼做,我首先會需要一個 button
的物件以及 type 是 file 的 input 元件
。
<template>
<div>
<input
type="file"
class="upload"
name="imgUpload"
multiple="multiple"
/>
<button>上傳照片</button>
</div>
</template>
這邊要記得把 input 加上 multiple="multiple"
不然沒法多選檔案
然後把這個 input 物件給隱藏起來,因為畢竟原生的物件樣式沒有很好看,我這邊用了很多種藏起來的方式 (笑。
.upload {
position: fixed;
top: -500px;
left: -500;
z-index: -100;
opacity: 0;
}
接下來才是重點,我們要點擊 button 來觸發 input 的 click 這個動作,才能開啟系統的選檔案視窗,所以我先新增一個 ref 變數來抓取 input 的實體。
<script>
import { ref } from "vue";
export default {
setup() {
// input DOM
const inputDOM = ref(null);
const fileChange = (e) => {
console.log(e.target.files);
};
const uploadImages = () => {
inputDOM.value.click();
};
return {
inputDOM,
fileChange,
uploadImages,
};
},
};
</script>
<template>
<div>
<input
ref="inputDOM"
type="file"
class="upload"
name="imgUpload"
multiple="multiple"
@change="fileChange"
/>
<button @click="uploadImages">上傳照片</button>
</div>
</template>
當我點擊 button 的時候去觸發 input 的 click 函式,這樣就可以開啟系統的選取檔案視窗,然後當我選取的檔案後,它就會觸發 change
事件,然後我們就可以取得的到我們 input 的 file 物件,長成這樣。
可以拿到物件之後我們就可以開始轉換成預覽用的 Blob 物件。
因為我們的 file 專換成 Blob 是一個 input 與 output 的動作,而且可能會依照不同的需求轉換的中間過程會需要不同的處理,所以這邊我們就很適合把整個轉換的動作邏輯包成 Composition API。
首先我要檢查 URL 這個物件有沒有 在window 之中,因為不同的瀏覽器可能名字不一樣,這點很重要。
window.URL = window.URL || window.webkitURL;
在來我們要新增我們的 function
export function useFileUpdate() {
// 預覽用檔案
const previewMap = ref({});
// 初始化
const initData = () => {
previewMap.value = {};
};
// 選擇多個檔案
const setFile = async (file = []) => {
initData();
previewMap.value = useQueuePreview(file);
};
return { setFile, previewMap };
}
這個 Composition API 主要會丟出兩個東西,一個是負責接收File檔案轉換的 function,一個是轉換好的檔案物件,讓我們可以直接跑一個 v-for
render的物件,所以你看這邊我 return 的兩個東西出去。
setFile
這個 function 裡面有兩個東西,一個是 initData
函式,一個是 useQueuePreview
的 Composition API ,我們先來看 initData
函式,這是為了每一次在選取要預覽的圖片的時候,先去清空前一次選取的圖片所需要做的,讓整個上傳的行為變得正常。
至於 useQueuePreview
呢 ? 因為我們傳入的 File 物件是多張圖片,所以它是一個陣列,所以我需要透過迴圈,一張張的做轉換,所以我在這個檔案裡面寫了一個新的 Composition API,因為這個只會用在 useFileUpdate
裡面,所以我就不拆出去了。
// 本地預覽
function useQueuePreview(fileArr) {
// 多圖多影片列表
const previewMap = {};
// 排序索引
let idx = 0;
for (const file of fileArr) {
const fileData = useImageFilePreview(file);
previewMap[idx] = fileData;
idx++;
}
return previewMap;
}
// 讀取 image 資料
function useImageFilePreview(file) {
return window.URL.createObjectURL(file);
}
我跑了一個迴圈,然後把資料格式重組成 Map 格式,至於為什麼不直接用成陣列就好,請參考前幾個章節的內容,迴圈的執行內容我又在另外的寫了一個useImageFilePreview
函式去做處理,把每個細節的動作都另外拆開,我可以很清楚的知道每個轉換的階段做了什麼事情。
完整的 useFileUpdate.js
的 code
import { ref } from "vue";
window.URL = window.URL || window.webkitURL;
// 讀取 image 資料
function useImageFilePreview(file) {
return window.URL.createObjectURL(file);
}
// 本地預覽
function useQueuePreview(fileArr) {
// 多圖多影片列表
const previewMap = {};
// 排序索引
let idx = 0;
for (const file of fileArr) {
const fileData = useImageFilePreview(file);
previewMap[idx] = fileData;
idx++;
}
return previewMap;
}
export function useFileUpdate() {
// 預覽用檔案
const previewMap = ref({});
// 初始化
const initData = () => {
previewMap.value = {};
};
// 選擇多個檔案
const setFile = async (file = []) => {
initData();
previewMap.value = useQueuePreview(file);
console.log(previewMap.value);
};
return { setFile, previewMap };
}
這樣一來,我們就可以在使用 useFileUpdate 的地方取得轉換成預覽圖的物件,直接使用。
我們來看一下使用了 setFile
後,取得 previewMap
,加一個 <img />
跑一下 v-for
吧
<script>
import { ref } from "vue";
import { useFileUpdate } from "./composition-api/useFileUpdate.js";
export default {
setup() {
const { setFile, previewMap } = useFileUpdate();
// input DOM
const inputDOM = ref(null);
const fileChange = (e) => {
console.log(e.target.files);
setFile(e.target.files);
};
const uploadImages = () => {
inputDOM.value.click();
};
return {
inputDOM,
fileChange,
uploadImages,
previewMap,
};
},
};
</script>
<template>
<div>
<input
ref="inputDOM"
type="file"
class="upload"
name="imgUpload"
multiple="multiple"
@change="fileChange"
/>
<button @click="uploadImages">上傳照片</button>
</div>
<div
v-show="Object.values(previewMap).length !== 0"
class="img_box"
v-for="item in previewMap"
:key="item"
>
<img :src="item" alt="" />
</div>
</template>
codesandbox 完成範例 : https://codesandbox.io/s/vue3-upload-img-preview-c629o?file=/src/App.vue:0-938
老闆或是客戶的腦袋永遠是你無法掌握的,我們雖然可以看到預覽圖了,但是圖片不足的地方就會露出黑色,畢竟使用者上傳圖片的時候我們不能去限制它上傳的圖片大小,所以我們需要把現在的黑底變成這樣有模糊的樣子。
這樣是看起來比較美觀啦,只是今天的篇幅夠長了,我想還是留一點到明天再說吧,那我們明天一起在來完成它,幫它加上模糊的效果。
Ps. 購買的時候請登入或註冊該平台的會員,然後再使用下面連結進入網站點擊「立即購課」,這樣才可以讓我獲得更多的課程分潤,還可以幫助我完成更多豐富的內容給各位。
我有開設了一堂專門針對Vue3從零開始教學的課程,如果你覺得不錯的話,可以購買我課程來學習
https://hiskio.com/bundles/9WwPNYRpz?s=tc
那如果對於JS基礎不熟的朋友,我也有開設JS的入門課程,可以參考這個課程
https://hiskio.com/bundles/b9Rovqy7z?s=tc
Mike 的 Youtube 頻道
Mike的medium
MIke 的官方 line 帳號,好友搜尋 @mike_cheng